# -*- coding: utf-8 -*-
# vim:et:sw=4:sts=4:ai:nosi
# Copyright (c) 2012 Flibusta Quotes Team
#
# This file is part of Flibusta Quotes.
#
# Flibusta Quotes is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from datetime import datetime
from hashlib import md5
import webapp2
from webapp2_extras import jinja2
from google.appengine.api import memcache
from util import datetime_to_float_str, float_to_datetime

from config import DEFAULT_MEMCACHE_TIME


class NotFoundException(Exception):
    pass

class UnknownParameterTypeError(Exception):
    pass


class BaseHandler(webapp2.RequestHandler):
    params = []
    post_params = []
    memcache_key = None
    memcache_time = DEFAULT_MEMCACHE_TIME

    @webapp2.cached_property
    def jinja2(self):
        """Return cached jinja2 Environment"""
        return jinja2.get_jinja2(app=self.app)

    def _render_response(self, template_name, **context):
        """Render a response using template and context,
           caching that response if needed"""
        # Extract memcache key and time from the method parameters,
        #  using class variables as defaults.
        memcache_key = context.pop('memcache_key',
                            self.memcache_key)
        memcache_time = context.pop('memcache_time',
                            self.memcache_time)

        # Render template
        rv = self.jinja2.render_template(template_name, **context)
        if memcache_key:
            # Cache results
            memcache.set(memcache_key, rv, time=memcache_time)
        # Return response
        self.response.write(rv)

    def render_error(self, errno, error_message):
        """Render error page"""
        if (errno != -1):
            # Set HTTP error code
            self.error(errno)
        # Construct memcache key
        memcache_key = 'error:%d' % errno
        if error_message:
            memcache_key += ':' + md5(error_message.encode('utf-8')).hexdigest()
        # Render response, if it isn't in the cache
        if not self._write_cached_response(memcache_key):
            self._render_response(
                'error.html',
                error = error_message,
                memcache_key = memcache_key
            )

    def _write_cached_response(self, memcache_key=None):
        """Respond with a cached page if there is one"""
        cached_response = memcache.get(memcache_key or self.memcache_key)
        if cached_response:
            # If there is a cached page, return it
            self.response.write(cached_response)
            return True

    @staticmethod
    def simple_get_method(func):
        """Decorator for cached views"""
        def wrapper(self, *args, **kwargs):
            response = memcache.get(self.memcache_key)
            if response:
                self.response.write(response)
            else:
                result = func(self, *args, **kwargs)
                try:
                    template_name, context = result
                except ValueError:
                    template_name, context = result, {}
                self._render_response(template_name, **context)
        return wrapper

    def _get_params(self, method='get', **kwargs):
        """Return dict of converted request parameters"""
        params = {}
        if method == 'get':
            parameters = self.params
        else:
            parameters = self.post_params
        # Get all possible request parameters
        for param_name, param_type in parameters:
            if param_name in kwargs:
                val = kwargs[param_name]
            else:
                val = self.request.get(param_name)
            if not val:
                params[param_name] = None
            else:
                if param_type == 'datetime':
                    try:
                        params[param_name] = float_to_datetime(float(val))
                    except (ValueError, OverflowError):
                        self.abort(500)
                elif param_type == 'int':
                    try:
                        params[param_name] = int(val)
                    except ValueError:
                        self.abort(500)
                elif param_type == 'unicode':
                    if not isinstance(val, unicode):
                        try:
                            val = val.decode('utf-8')
                        except UnicodeDecodeError:
                            self.abort(500)
                    params[param_name] = val
                else:
                    raise UnknownParameterTypeError('Unknown parameter type')
        return params

    @classmethod
    def _get_key(cls, **kwargs):
        """Construct and return memcache key"""
        key = cls.memcache_key
        for k in sorted(kwargs):
            val = kwargs[k]
            if val:
                if isinstance(val, datetime):
                    key += ':' + datetime_to_float_str(val)
                elif isinstance(val, int) or isinstance(val, long):
                    key += ':%d' % val
                else:
                    key += ':' + val
        return key

    @staticmethod
    def get_method(func):
        """Decorator for cached views with request parameters"""
        def wrapper(self, **kwargs):
            params = self._get_params(**kwargs)
            key = self._get_key(**params)
            response = memcache.get(key)
            if response:
                self.response.write(response)
            else:
                try:
                    template_name, context = func(self, **params)
                except NotFoundException, e:
                    self.render_error(404, e.message)
                else:
                    self._render_response(template_name, memcache_key=key, **context)
        return wrapper

    @staticmethod
    def get_method_no_cache(func):
        """Decorator for views with request parameters (without caching)"""
        def wrapper(self, **kwargs):
            params = self._get_params(**kwargs)
            try:
                template_name, context = func(self, **params)
            except NotFoundException, e:
                self.render_error(404, e.message)
            else:
                self._render_response(template_name, memcache_key=None, **context)
        return wrapper

    @staticmethod
    def post_method(func):
        """Decorator for post views"""
        def wrapper(self, **kwargs):
            params = self._get_params('post', **kwargs)
            result = func(self, **params)
            if result:
                try:
                    template_name, context = result
                except ValueError:
                    self.redirect(result)
                else:
                    self._render_response(template_name, **context)
        return wrapper


def BasicTemplateHandler(template_name):
    """Simple template renderer"""
    class cls(BaseHandler):
        def get(self):
            self._render_response(
                template_name,
                memcache_key = 'tmpl:' + template_name
            )

    return cls

